package cn.bugstack.mybatis.plugin;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

/**
 * 插件：将拦截器 正确匹配到  (plugin interceptor into right place)
 * @author xingshujing
 */
public class Plugin implements InvocationHandler {

    /**
     * 目标对象
     */
    private Object target;

    /**
     * 要执行的拦截器
     */
    private Interceptor interceptor;

    /**
     * @description @interceptors 标注的信息
     * key：target class
     * values： target methods
     */
    private Map<Class<?>, Set<Method>> signatureMap;

    public Plugin(Object target, Interceptor interceptor, Map<Class<?>, Set<Method>> signatureMap) {
        this.target = target;
        this.interceptor = interceptor;
        this.signatureMap = signatureMap;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        Set<Method> methods = signatureMap.get(method.getDeclaringClass());
        if (methods != null && methods.contains(method)) {
            return interceptor.intercept(new Invocation(target, method, args));
        }
        return method.invoke(target,args);
    }

    /**
     * @target use proxy wrap interceptor into target
     */
    public static Object wrap(Object target, Interceptor interceptor) {
        Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
        // 取得要改变行为的类(ParameterHandler|ResultSetHandler|StatementHandler|Executor)，目前只添加了 StatementHandler
        Class<?> type = target.getClass();
        // 获取所有被拦截的接口
        Class<?>[] interfaces = getAllInterfaces(type,signatureMap);
        if (interfaces.length > 0) {
            return Proxy.newProxyInstance(type.getClassLoader(), interfaces, new Plugin(target, interceptor, signatureMap));
        }
        // target 没有实现接口那么就不进行代理
        return target;
    }

    private static Class<?>[] getAllInterfaces(Class<?> type, Map<Class<?>, Set<Method>> signatureMap) {
        Set<Class<?>> interfaces = new HashSet<>();
        while (type != null) {
            for (Class<?> c : type.getInterfaces()) {
                if (signatureMap.containsKey(c)) {
                    interfaces.add(c);
                }
            }
            type = type.getSuperclass();
        }
        return interfaces.toArray(new Class[interfaces.size()]);
    }


    /**
     * @target get signatureMap from an interceptor
     *
     */
    private static Map<Class<?>, Set<Method>> getSignatureMap(Interceptor interceptor) {
        Map<Class<?>, Set<Method>> signatureMap = new HashMap<>();
        Intercepts interceptorAnnotation = interceptor.getClass().getAnnotation(Intercepts.class);
        Signature[] signatures = interceptorAnnotation.value();
        for (Signature signature : signatures) {
            Set<Method> methods = signatureMap.computeIfAbsent(signature.type(), k -> new HashSet<>());
            try {
                String methodName = signature.method();
                Class<?>[] args = signature.args();
                Method method = signature.type().getMethod(methodName, args);
                methods.add(method);
            } catch (NoSuchMethodException e) {
                throw new RuntimeException("Could not find method on " + signature.type() + " named " + signature.method() + ".  Cause: " + e, e);
            }
        }
        return signatureMap;
    }
}
